<?php
/**
 * 将LuaBase数据协议封装的数据进行解包和打包
 * 
 * 使用方法:
 * 解包：
 * $bsp = new bsp();
 * $array = $bsp->unpack($bspstring);
 * 打包：
 * $bsp = new bsp();
 * $data = $bsp->pack($array);
 * 
 * 运行在32bits和64bits系统中，在对数字进行转换时，可能生成不同类型的结果
 * 
 * @author n3yang@gmail.com
 *
 */
class Bsp {

	/**
	 * byte类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_BYTE		= 0x1;
	/**
	 * short类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_SHORT		= 0x2;
	/**
	 * int类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_INT		= 0x3;
	/**
	 * longlong类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_LONGLONG	= 0x4;
	/**
	 * float类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_FLOAT		= 0x5;
	/**
	 * string类型标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_STRING		= 0x6;
	/**
	 * 数组开始标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_ARRAY_START	= 0xF0;
	/**
	 * 数组结束标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_ARRAY_END	= 0xF1;
	/**
	 * null标识符
	 * @var int
	 */
	const PKG_VALUE_TYPE_NULL		= 0xFF;
	/**
	 * 类型标识符长度数组，string类型采用1位类型+4位字符串长度方式，共计5位
	 * @var Array
	 */
	static $typelens = array(
		self::PKG_VALUE_TYPE_BYTE		=> 1,
		self::PKG_VALUE_TYPE_SHORT		=> 2,
		self::PKG_VALUE_TYPE_INT		=> 4,
		self::PKG_VALUE_TYPE_LONGLONG	=> 8,
		self::PKG_VALUE_TYPE_FLOAT		=> 8,
		self::PKG_VALUE_TYPE_STRING		=> 1, //header = type (1) + string length (4)
	);
	/**
	 * bsp二进制字符串
	 * @var string
	 */
	protected $bspstr = '';
	/**
	 * bsp数组
	 * @var array
	 */
	protected $bsparray = array();
	/**
	 * 运行环境标识符
	 * @var bool
	 */
	protected $is_on64bit = true;
	/**
	 * 根据类型标识符获取类型长度
	 * @param int $type类型标识符
	 * @return int 类型长度，类型非法返回0
	 */
	protected function getTypeLen($type){
		if ( !empty(self::$typelens[$type]) ){
			return self::$typelens[$type] ;
		} else {
			return 0;
		}
	}
	/**
	 * 将网络字节序转换成主机字节序
	 * @param string 
	 * @return string
	 */
	public function ntohl($str){
		$arr = unpack('I', pack('N', $str));
		return $arr[1];
	}

	/**
	 * 在64bit系统中将longlong从网络字节序中解包
	 * @param string $str
	 * @return number
	 */
	protected function getLonglong64bit($str){
		list ($hi, $lo) = array_values(unpack('N*N*', $str));
		if ($hi<0) $hi += (1 << 32);
		if ($ho<0) $lo += (1 << 32);
		$res = ($hi << 32) + $lo;
		return $res;
	}
	
	/**
	 * 将bsp字符串解包
	 * @return array 解完成的数组
	 */
	public function unpack($bspinput){
		if (is_string($bspinput) && strlen($bspinput)>=8){
			$this->bspstr = substr($bspinput, 8);
		} else {
			return array();
		}
		$i = 1;
		while(strlen($this->bspstr)>0){
			$typelen = $realstr = $realstr_len = '';
			$type = unpack('C', $this->bspstr[0]);
			$type = $type[1];
			$typelen = $this->getTypeLen($type);
			switch ($type) {
				case self::PKG_VALUE_TYPE_BYTE:
					$realstr = unpack('C', substr($this->bspstr, 1, $typelen));
					$realstr = $realstr[1];
					break;
				case self::PKG_VALUE_TYPE_SHORT:
					$realstr = unpack('n', substr($this->bspstr, 1, $typelen));
					$realstr = $realstr[1];
					break;
				case self::PKG_VALUE_TYPE_INT:
					$temp = unpack('I', substr($this->bspstr, 1, $typelen));
					// 需要将网络字节序转换成主机字节序
					$realstr = $this->ntohl($temp[1]);
					break;
				case self::PKG_VALUE_TYPE_LONGLONG:
					$longstr = substr($this->bspstr, 1, $typelen);
					// 在64位系统中可以直接使用位移方式还原
					// 但在32位系统，由于int长度的限制，只能获取到近似精确度的浮点数
					if ($this->is_on64bit){
						$realstr = $this->getLonglong64bit($longstr);
					} else {
						list($hi, $lo) = array_values(unpack('N*N*', $longstr));
						$realstr = $hi*pow(2, 32) + $lo;
					}
					break;
				case self::PKG_VALUE_TYPE_FLOAT:
					// 浮点类型在精度上还有问题，需要进一步测试
					$realstr = unpack('d', substr($this->bspstr, 1, $typelen));
					$realstr = $realstr[1];
					break;
				case self::PKG_VALUE_TYPE_STRING:
					// 获取字符串长度： header = type length(1) + string length(4)
					$temp = unpack('I', substr($this->bspstr, 1, 4));
					// 获取真实字符串长度并解包
					$length = $this->ntohl($temp[1]);
					$temp = unpack('C'.$length, substr($this->bspstr, $typelen+4, $length));
					foreach ($temp as $k=>$v){
						$realstr .= chr($v);
					}
					// 记录此段的真实长度
					$realstr_len = 3 + $length;
					break;
				case self::PKG_VALUE_TYPE_ARRAY_START:
					$realstr = '{';
					break;
				case self::PKG_VALUE_TYPE_ARRAY_END:
					$realstr = '}';
					break;
				case self::PKG_VALUE_TYPE_NULL:
					break;
					
				default:
					;
				break;
			}
			
 			//echo 'type:'.$type.',str:'.$realstr."\n";
			$this->bspstr = substr($this->bspstr, 1+$typelen+$realstr_len);
			if ($realstr==='{') {
				$jsonstr .= $realstr;
			} else if ($realstr ==='}'){
				$jsonstr = substr($jsonstr, 0, strlen($jsonstr)-1) . $realstr . ',';
				$i++;
			} else {
				// 为key和非数字value加上单引号。如果是在32位系统上，为最大限度的还原数据，number也会转换成string
				$jsonstr .= (!is_numeric($realstr) || $i%2 || (!$this->is_on64bit))
					? '"' . addcslashes($realstr, '\"') . '"'
					: $realstr;
				$jsonstr .= $i%2 ? ':' : ',';
			}
			$i++;
		}
	 	$jsonstr = substr($jsonstr, 0, -1);
		$this->bsparray = json_decode('{'.$jsonstr.'}', true);
		return $this->bsparray;
	}
	
	/**
	 * 将bsparray打包成bsp的二进制流
	 * @return string bsp数据流
	 */
	public function pack($bspinput){
		if (!is_array($bspinput) || empty($bspinput)){
			return '';
		} else {
			$this->bsparray = $bspinput;
		}
		$this->bspstr = $this->getBspstrFromArray($this->bsparray);
		$this->command_length = strlen($this->bspstr);
		$this->bspstr = pack('N', $this->command_id) . pack('N', $this->command_length) . $this->bspstr;
		return $this->bspstr;
	}
	
	/**
	 * 将数组打包成bsp二进制数据
	 * @param array $array
	 * @return string
	 */
	protected function getBspstrFromArray($array){
		$bspstr = '';
		foreach ($array as $k=>$v){
			$bspstr .= $this->getPackedStr($k);
			$bspstr .= $this->getPackedStr($v);
		}
		return $bspstr;
	}
	
	/**
	 * 将输入的字符串或者数组转换成bsp格式数据
	 * @param mixied $input数组或者字符串
	 * @return string 打包后的bsp二进制数据
	 */
	public function getPackedStr($input=''){
		if (is_int($input)){
			if ($input>0xFFFFFFFF){
				$return = pack('C', self::PKG_VALUE_TYPE_LONGLONG);
				$return .= pack("NN", $input>>32, $input&0xFFFFFFFF);
			} else {
				$return = pack('C', self::PKG_VALUE_TYPE_INT);
				$return .= pack('N', $input);
			}
		}else if (is_string($input)){
			$return = pack('C', self::PKG_VALUE_TYPE_STRING);
			$inputlen = strlen($input);
			$return .= pack('N', $inputlen);
			for($i=0;$i<$inputlen;$i++){
				$return .= pack('C', ord($input[$i]));
			}
		}else if (is_float($input)){
			// 32位系统会将超过范围的int转成float，所以我们这里也当做float来pack
			$return = pack('C', self::PKG_VALUE_TYPE_FLOAT);
			$return .= pack('d', $input);
		}else if (is_array($input)){
			$return = pack('C', self::PKG_VALUE_TYPE_ARRAY_START);
			$return .= $this->getBspstrFromArray($input);
			$return .= pack('C', self::PKG_VALUE_TYPE_ARRAY_END);
		}
		return !isset($return) ? '' : $return;
	}
	
	/**
	 * 
	 * @param mixed $bspinput输入数据
	 */
	function __construct(){
		// running on 32-bit machine ?
		$this->is_on64bit = ((1<<32)==1) ? false : true;
	}
	
}

